-
Notifications
You must be signed in to change notification settings - Fork 731
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Auto Persisted Queries with CDN Support #608
Feature: Auto Persisted Queries with CDN Support #608
Conversation
@Codebear98 we've got some changes in progress with this with #599 and #602 - you may want to hold off until those get merged! |
cool, make sense for #599, probably can align with apollo-android. |
9748016
to
9e69402
Compare
PR updated following the design of #599 and #602 @designatednerd please take a look, thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this has a lot of the same stuff in it as #583 - let's get that one dealt with first. I'm going to mark this as changes requested until that gets merged to avoid accidentally merging this one.
One thing that @SirensOfTitan pointed out when I closed his PR is that this ties APQ to the |
Probably refactor to decouple the request payload part out from NetworkTransport? |
9e69402
to
55bb400
Compare
2. remove `_` on private variable 3. add guard clause to optional variable in HttpNetworkTransportTests 4. removed redundant parameter in StarWarsServerTests 5. change // Mark to // MARK 6. single line for method declaration 7. removed redundant code in HTTPNetworkTransport 9. update switch style
…former for SDK lower than 11.
let url: URL | ||
let session: URLSession | ||
var session: URLSession |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does this now need to be a var
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, It's used to assign MockSession to intercept the Request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok - let's go with this for now. I'm planning to get some stuff allowing a URL session to be passed in with an initializer soon.
let response = GraphQLResponse(operation: operation, body: body) | ||
completionHandler(response, nil) | ||
if self.enableAutoPersistedQueries, | ||
let error = body["errors"] as? [JSONObject], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is getting bit messy here... How about creating GraphQLResponse
(else path) and adding method to it to get errors. That way you don't need to know here much about JSONObject
and GraphQLResponse
already knows about it.
let response = GraphQLResponse(operation: operation, body: body)
if response.errors.contain(...) {
} else {
completionHandler(response, nil)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea, will update accordingly!
renamed useHttpGetMethodForPersistedQueries to useGETForPersistedQueryRetry un-shared test projects fixed grammer typo in GraphQLHTTPResponseError updated to use XCTAssertEqual
|
||
var payload: GraphQLMap = [:] | ||
|
||
if autoPersistQueries { | ||
guard let operationIdentifier = operation.operationIdentifier else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if this should be preconditionFailure
. You might want persist queries for some but not all request. Wouldn't it be better to just do normal query if operation.operationIdentifier
is nil
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for reviewing.
But u can't specify which request to use persistQueries but not all.
And all or none operationIdentifier will be generated according to Apollo run-script.
if operationIdentifier missing and you wanna enable APQs, definitely indicate there is an issue in Apollo Codegen script.
Did I miss something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
--operationIdsPath
is path to JSON file that is tooling dependant and/or can be modified as you see fit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The question here is Do we need to support autoPersistQueries just for some requests but not for all?
As the autoPersistQueries seems network traffic optimisation like CDN caching which should be applying to all or none because it depends server side ability to support or not.
I just worry if we don't preconditionFailure
for the pre-gen code that's not fulfilling the APQs requirement (but dev opt to turn on APQs). Developers will not be able to get alert about the run script missing operationIdsPath
until they look into the traffic logging and find out APQs isn't in place.
In addition, I think the JSON file will not affect the code gen? I mean even you modify the JSON file, it won't affect operation.operationIdentifier in the generated Code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC the operation identifier is based on the hash of the body, so it shouldn't change based on the location of the codegen.
Personally I'm good with leaving this as a precondition failure - @Codebear98 is correct, if someone wants APQ's and hasn't set up generating operation IDs, we want this to fail loud, hard, and early.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fair enough
guard | ||
let delegate = self.delegate, | ||
let retrier = delegate as? HTTPNetworkTransportRetryDelegate else { | ||
// retryByDefault: still retry if delegate is absent, otherwise repect the value from delegate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this comment should now be removed
_ = self.send(operation: operation, retryFor: error, completionHandler: completionHandler) | ||
}) | ||
} else if retryByDefault { | ||
_ = self.send(operation: operation, retryFor: error, completionHandler: completionHandler) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still have this concern
request.setValue("application/json", forHTTPHeaderField: "Content-Type") | ||
request.setValue(operation.operationIdentifier, forHTTPHeaderField: "X-APOLLO-OPERATION-ID") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you may need to either rebase or merge in from master, there's some stuff there around this that's checking whether there's an operation ID before sending this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, and I would also add operationName
in APQs request body, as it's available now thanks for 2.16 tooling upgrade!
|
||
class APQsWithGetMethodConfig: TestConfig, HTTPNetworkTransportRetryDelegate{ | ||
func networkTransport(_ networkTransport: HTTPNetworkTransport, receivedError error: Error, for request: URLRequest, response: URLResponse?, retryHandler: @escaping (Bool) -> Void) { | ||
retryHandler(true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to check that the received error is of the correct type? Otherwise this could lead to an infinite loop.
|
||
import Foundation | ||
|
||
public final class MockURLSession: URLSession { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does all this need to be public
?
XCTAssertEqual(url?.absoluteString, "http://localhost:8080/graphql?query=query%20HeroName($episode:%20Episode)%20%7B%0A%20%20hero(episode:%20$episode)%20%7B%0A%20%20%20%20__typename%0A%20%20%20%20name%0A%20%20%7D%0A%7D&variables=%7B%22episode%22:%22EMPIRE%22%7D") | ||
let queryString = url?.absoluteString == "http://localhost:8080/graphql?query=query%20HeroName($episode:%20Episode)%20%7B%0A%20%20hero(episode:%20$episode)%20%7B%0A%20%20%20%20__typename%0A%20%20%20%20name%0A%20%20%7D%0A%7D&variables=%7B%22episode%22:%22EMPIRE%22%7D" | ||
|
||
XCTAssertTrue(queryString) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this changed?
XCTFail("variables should not nil") | ||
return | ||
} | ||
XCTAssertEqual(variables, "{\"episode\":null}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can also check variables.keys.contains("episode")
and then check that the value for that is null rather than checking the raw string.
@@ -109,6 +299,6 @@ class GETTransformerTests: XCTestCase { | |||
let transformer = GraphQLGETTransformer(body: body, url: self.url) | |||
|
|||
let url = transformer.createGetURL() | |||
XCTAssertNil(url) | |||
XCTAssertEqual(url?.absoluteString, "http://localhost:8080/graphql?variables=%7B%22episode%22:%22EMPIRE%22%7D") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I wound up deleting this test in master, you're welcome to as well.
@Codebear98 Do you have interest in continuing this, or do you mind if I take this PR over? |
@designatednerd sorry, I don’t wanna delay the progress, please take it over if u would, thanks! |
This feature can now be followed at #767 |
Usage
Features
HTTPGetMethod
for all APQs Requests by settinguseGETForQueries=true
HTTPGetMethod
only for supported APQs request by settinguseGETMethodForPersistedQueries=true
, according to https://www.apollographql.com/docs/apollo-server/features/apq/#cdnuseGETMethodForPersistedQueries=true
is aligning with apollo-android #1376PersistedQueryNotFound
orPersistedQueryNotSupported
POST
Request (retry withGET
whenuseGETForQueries=true
) and includesQueryDocument
for registering APQs.useGETMethodForPersistedQueries
is introduced due to an errorexceeds limit on url query string
may occur on some sever when query string exceeds 2k limit with large amount of data such as QueryDoucment.Limitation
To enable APQs,
sha256hash
ofQueryDocument
is required for eachQueryOperation
.You need to Modify Apollo runscript in
YOUR PROJECT
to also generateoperationIdentifier
(sha256hash
) to your API.swift (please see below)Must use Apollo 1.9.2, since there is a bug in
Apollo-tooling
while generatingoperationIdentifier
The Query and hash does not match when the
Query
containsfragment
. It's due to an extra\n
added inQueryDocument
for hashing. But the\n
is missing in Swift CodeGen.operationId is invalid for swift codegen apollo-tooling#1362
There is a hacking workaround in
HTTPNetworkTransport.swift
line212, It adds back the\n
to theQueryDocument
.Apollo runscript
add
--operationIdsPath=operationIdsPath
for swift codegen